﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;
using System.Reflection;
using System.Web.Handlers;
using System.Web.SessionState;

namespace ExtenDev.Web.Extensions
{
    public class ControlMethodImpl
    {
        #region Static Reflection Members

        private static MethodInfo getWebServiceData;
        private static MethodInfo isRestCall;
        private static MethodInfo isRestRequest;
        private static MethodInfo getMethodData;
        private static MethodInfo executeWebServiceCall;

        static ControlMethodImpl()
        {
            Type scriptModule = typeof(ScriptModule);
            Assembly sysWebExt = scriptModule.Assembly;
            Type restHandler = sysWebExt.GetType("System.Web.Script.Services.RestHandler");
            Type restHandlerFactory = sysWebExt.GetType("System.Web.Script.Services.RestHandlerFactory");
            Type webServiceData = sysWebExt.GetType("System.Web.Script.Services.WebServiceData");

            isRestCall = restHandlerFactory.GetMethod("IsRestMethodCall", BindingFlags.Static | BindingFlags.NonPublic);
            isRestRequest = restHandlerFactory.GetMethod("IsRestRequest", BindingFlags.Static | BindingFlags.NonPublic);
            getWebServiceData = webServiceData.GetMethod("GetWebServiceData",
                BindingFlags.Static | BindingFlags.NonPublic,
                null,
                new Type[] { typeof(HttpContext), typeof(string), typeof(bool), typeof(bool) },
                null);
            getMethodData = webServiceData.GetMethod("GetMethodData", BindingFlags.NonPublic | BindingFlags.Instance);
            executeWebServiceCall = restHandler.GetMethod("ExecuteWebServiceCall", BindingFlags.Static | BindingFlags.NonPublic);
        }

        #endregion

        #region Instance Reflection Wrappers

        protected bool IsRestMethodCall(HttpRequest request)
        {
            return (bool)isRestCall.Invoke(null, new object[] { request });
        }

        protected bool IsRestRequest(HttpContext context)
        {
            return (bool)isRestRequest.Invoke(null, new object[] { context });
        }

        protected object GetWebServiceData(HttpContext context, string virtualPath)
        {
            return GetWebServiceData(context, virtualPath, true, false);
        }

        protected object GetWebServiceData(HttpContext context, string virtualPath, bool failIfNoData, bool pageMethods)
        {
            return getWebServiceData.Invoke(null, new object[] { context, virtualPath, failIfNoData, pageMethods });
        }

        protected object GetMethodData(object webServiceData, string methodName)
        {
            return getMethodData.Invoke(webServiceData, new object[] { methodName });
        }

        protected void ExecuteWebServiceCall(HttpContext context, object methodData)
        {
            executeWebServiceCall.Invoke(null, new object[] { context, methodData });
        }

        #endregion

        /// <summary>
        /// Determines whether or not the request matches the basic requirements (generally file path/extension) to even apply Control Methods to.
        /// </summary>
        /// <remarks>This can be overridden to allow control over which requests will try to process a REST request</remarks>
        /// <param name="request"></param>
        /// <returns></returns>
        public virtual bool ShouldCheckRequest(HttpRequest request)
        {
            string filePath = request.Path;
            int midx = filePath.LastIndexOf("/");
            if (midx > 0)
            {
                filePath = filePath.Remove(midx);
                return filePath.EndsWith(".ascx", StringComparison.OrdinalIgnoreCase) ||
                    filePath.EndsWith(".master", StringComparison.OrdinalIgnoreCase);
            }
            return false;
        }

        /// <summary>
        /// Applies a session capable handler to the request to ensure session is loaded when the request may need to be processed.
        /// </summary>
        /// <remarks>This is generally applied during the HttpApplication PostMapRequestHandler event</remarks>
        /// <param name="application">The current HttpApplication for the request</param>
        public virtual void ApplySessionRequestHandler(HttpApplication application) {
            HttpRequest request = application.Context.Request;
            HttpContext context = application.Context;

            if (ShouldCheckRequest(request) && IsRestMethodCall(request) && !(context.Handler is IRequiresSessionState))
            {
                context.Handler = new SessionCapableHandler(context.Handler);
            }
        }

        public virtual bool ProcessRestRequest(HttpApplication application)
        {
            HttpContext context = application.Context;
            HttpRequest request = context.Request;

            if (context.Handler is IRequiresSessionState &&
                ShouldCheckRequest(request) && IsRestMethodCall(request))
            {
                object data = GetWebServiceData(context, request.FilePath, false, true);
                if (data == null)
                {
                    return false; // Throw exception? This means there was no method or data, depending on the circumstance.
                }

                string methodName = request.PathInfo.Substring(1);
                object methodData = GetMethodData(data, methodName);
                ExecuteWebServiceCall(context, methodData);
                application.CompleteRequest();

                return true;
            }
            return false;
        }
    }
}
